﻿using System;
using System.Data;
using System.Text;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using net.veekee.common.data;

namespace net.veekee.common.rules
{
    public class RuleManagerFactory
    {
        public static RuleManager<Object, AnEntry> Create(Type type)
        {
            Log.GetInstance().Debug("Creating a rule manager for {0}", type.Name);
            Type listGeneric = typeof(RuleManager<,>);
            Type listConcrete = listGeneric.MakeGenericType(new Type[] { type });
            ConstructorInfo constructor = listConcrete.GetConstructor(new Type[0]);
            Object obj = (Object)constructor.Invoke(new Object[0]);
            return (RuleManager<Object, AnEntry>)obj;
        }
    }

    public class RuleManager<C, T> where T : AnEntry
    {
        private List<RuleSet<C, T>> rules = null;
        private List<RuleSet<C, T>> erroneousrules = null;
        private C assertedContext = default(C);
        private T assertedObject = default(T);

        public RuleManager()
        {
            this.rules = new List<RuleSet<C, T>>();
            this.erroneousrules = new List<RuleSet<C, T>>();
        }

        public void LoadRulesFromDirectory(String directory, String extension = "rule")
        {
            Log.GetInstance().Info("Loading rules from '{0}'", directory);
            if (!System.IO.Directory.Exists(directory))
            {
                Log.GetInstance().Error("Rules directory '{0}' doesn't exists", directory);
                return;
            }
            String[] files = Directory.GetFiles(directory, "*." + extension);
            Array.Sort<String>(files);
            foreach (String file in files)
            {
                RuleSetFactory<C, T>.LoadFromFile(file, this.rules, this.erroneousrules);
            }
            this.rules.Sort(delegate(RuleSet<C, T> x, RuleSet<C, T> y)
            {
                return (y.Salience - x.Salience);
            });
            Log.GetInstance().Info("Statistics: {0} rule{1} loaded / {2} rule{3} invalidated / {4} rule{5} discarded", RuleSetFactory<C, T>.LoadedRules, RuleSetFactory<C, T>.LoadedRules > 1 ? "s" : "", RuleSetFactory<C, T>.ErroneousRules, RuleSetFactory<C, T>.ErroneousRules > 1 ? "s" : "", RuleSetFactory<C, T>.DiscardedRules, RuleSetFactory<C, T>.DiscardedRules > 1 ? "s" : "");
            foreach (RuleSet<C, T> r in this.rules)
            {
                Log.GetInstance().Debug("\t- {0} (priority {1})", r.Name, r.Salience);
            }
            if (RuleSetFactory<C, T>.ErroneousRules > 0)
            {
                Log.GetInstance().Warn("WARNING - Errors detected in rules.");
                foreach (RuleSet<C, T> r in this.erroneousrules)
                {
                    Log.GetInstance().Debug("\t- {0} (from {1})", r.Name, r.RuleFileName);
                }
                Console.WriteLine("");
                Console.WriteLine("Please press \"Enter\" to continue");
                String line = Console.ReadLine();
                if (line == null)
                    Environment.Exit(127);
            }
        }

        public void AssertContext(C o)
        {
            this.assertedContext = o;
        }

        public void AssertObject(T o)
        {
            this.assertedObject = o;
        }

        public void FireAllRules()
        {
            Log.GetInstance().Trace("New Object to process: " + this.assertedObject.ToString());
            foreach (RuleSet<C, T> rule in this.rules)
            {
                Log.GetInstance().Trace("Selecting rule '{0}'", rule.Name);
                if(! rule.IsValid)
                    continue;
                try
                {
                    Log.GetInstance().Trace("Trying rule '{0}'", rule.Name);
                    if (!rule.CheckConditions(this.assertedContext, this.assertedObject))
                        continue;
                    Log.GetInstance().Trace("Rule '{0}' is beeing applied", rule.Name);
                    rule.Process(this.assertedContext, this.assertedObject);
                }
                catch(Exception e)
                {
                    Log.GetInstance().Error("Error while processing rule '{0}': {1}", rule.Name, e.ToString());
                }
            }
        }
    }
}
